// Copyright 2008 Thiago H. de Paula Figueiredo
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package br.com.arsmachina.tapestrycrud.hibernatevalidator.mixins;

import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.Field;
import org.apache.tapestry5.ValidationTracker;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.services.ThreadLocale;
import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;

import br.com.arsmachina.tapestrycrud.hibernatevalidator.services.ClassValidatorSource;
import br.com.arsmachina.tapestrycrud.services.FormValidationSupport;

/**
 * Mixin that is not meant to be used as a mixin, just as an easy way to invoke the
 * {@link DummyHibernateValidatorMixin#validate(Object, Form, ComponentResources)} method inside
 * page classes. This class exists because it is not possible to pass a {@link Form} instance (a
 * Tapestry-transformed classs) to a Tapestry-IoC service (not Tapestry-transformed).
 * 
 * @sse {@link HibernateValidatorMixin}
 * @author Thiago H. de Paula Figueiredo
 */
public class DummyHibernateValidatorMixin {

	/**
	 * Default error message format.
	 */
	private static final String DEFAULT_ERROR_MESSAGE_FORMAT = "%s: %s.";

	/**
	 * Error message format key.
	 */
	public static final String ERROR_MESSAGE_FORMAT = "tapestry.crud.hibernate.validator.format";

	@Inject
	private FormValidationSupport formValidationSupport;

	@Inject
	private ClassValidatorSource classValidatorSource;

	@Inject
	private Messages messages;

	@Inject
	private ThreadLocale threadLocale;

	/**
	 * Validates an object and records its erros in a given form. The same as
	 * <code>validate(object, form, componentResources, true)</code>.
	 * 
	 * @param object aon {@link Object}. It cannot be null.
	 * @param form a {@link Form}. It cannot be null.
	 * @param the {@link ComponentResources} from the page where the form belongs.
	 * 
	 * @return <code>true</code> if no validation errors were fond, <code>false</code> otherwise.
	 */
	public boolean validate(final Object object, final Form form,
			ComponentResources componentResources) {
		return validate(object, form, componentResources, true);
	}

	/**
	 * Validates an object and records its erros in a given form.
	 * 
	 * @param object aon {@link Object}. It cannot be null.
	 * @param form a {@link Form}. It cannot be null.
	 * @param the {@link ComponentResources} from the page where the form belongs.
	 * @param singleErrorMessagePerField a <code>boolean</code>. If <code>true</code>, it will
	 * record at most one error message per field.
	 * 
	 * @return <code>true</code> if no validation errors were fond, <code>false</code> otherwise.
	 */
	@SuppressWarnings("unchecked")
	public boolean validate(final Object object, final Form form,
			ComponentResources componentResources, boolean singleErrorMessagePerField) {

		final ClassValidator classValidator = classValidatorSource.get(object.getClass());
		final InvalidValue[] invalidValues = classValidator.getInvalidValues(object);
		final ValidationTracker tracker = form.getDefaultTracker();
		boolean addMessage;

		for (InvalidValue invalidValue : invalidValues) {

			final String propertyName = invalidValue.getPropertyName();
			Field field = formValidationSupport.getField(propertyName, componentResources);

			if (field != null) {
				addMessage = singleErrorMessagePerField == false || tracker.inError(field) == false;
			}
			else {
				addMessage = true;
			}

			if (addMessage) {

				String message = createMessage(field, invalidValue);

				if (field != null) {
					form.recordError(field, message);
				}
				else {
					form.recordError(message);
				}

			}

		}

		return invalidValues.length == 0;

	}

	/**
	 * Creates a message for a given <code>field</code> and <code>invalidValue</code>.
	 * 
	 * @param invalidValue an {@link InvalidValue}. It cannot be null.
	 * @param field a {@link Field}. It cannot be null.
	 * @return a {@link String}. It cannot be null.
	 * @todo: handle @@AssertTrue and @@AssertFalse
	 */
	protected String createMessage(Field field, InvalidValue invalidValue) {

		String fieldLabel;
		final String propertyName = invalidValue.getPropertyName();

		if (field != null) {
			fieldLabel = field.getLabel();
		}
		else {

			final String key = propertyName + "-label";
			fieldLabel = messages.contains(key) ? messages.get(key) : propertyName;

		}

		String format;

		if (messages.contains(ERROR_MESSAGE_FORMAT)) {
			format = messages.get(ERROR_MESSAGE_FORMAT);
		}
		else {
			format = DEFAULT_ERROR_MESSAGE_FORMAT;
		}

		return String.format(threadLocale.getLocale(), format, fieldLabel,
				invalidValue.getMessage());

	}

}
